﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Galoap
{


    partial class GameSession
    {
        /// <summary>
        /// method which get an existing question database
        /// </summary>
        /// <returns>a question</returns>
        virtual public Question GetQuestion()
        {
            this.GameReference.Load();
            this.Game.Question.Load();
            return this.Game.Question.Except(GetQuestions()).OrderBy(d => Guid.NewGuid()).First();
        }

        /// <summary>
        /// method which generates and get a new question on unused document if there are
        /// </summary>
        /// <typeparam name="T">the Question subtype</typeparam>
        /// <typeparam name="K">the Document's question subtype</typeparam>
        /// <param name="level">the level of the question required</param>
        /// <param name="unused">true if it generates a question on a new unused document</param>
        /// <returns>a T question initialized</returns>
        virtual public T GetQuestion<T, K>(int level, bool unused)
            where T : Question, new()
            where K : Document
        {
            return GetQuestion<T, K>(level, unused, 0.75f);
        } 

        /// <summary>
        /// method which gets a question from database or generates and gets a new question on unused document with a note probably
        /// </summary>
        /// <typeparam name="T">the Question subtype</typeparam>
        /// <typeparam name="K">the Document's question subtype</typeparam>
        /// <param name="level">the level of the question required</param>
        /// <param name="probably">the probably to give a question that exists on database</param>
        /// <param name="unused">true if it generates a question on a new unused document</param>
        /// <returns>a T question initialized</returns>
        virtual public T GetQuestion<T, K>(int level, bool unused, float probably)
            where T : Question, new()
            where K : Document
        {

           
            if (unused)
            {
                // it uses a new document 
                Random _r = new Random();
                this.GameReference.Load();
                this.Game.Question.Load();

                T retQuestion;

                // verify if there are a probably 
                if (_r.Next(100) < probably * 100)
                {
                    // use a question from DB
                    try
                    {
                        retQuestion = this.Game.Question.Except(this.GetQuestions(level)).OfType<T>().OrderBy(d => Guid.NewGuid()).First();
                    }
                    catch (InvalidOperationException)
                    {
                        // no more questions
                        try
                        {
                            // add a new question on database if there are unused document 
                            Document myDoc = Game.GetUnusedDocument<K>();
                            retQuestion = new T { Game = this.Game, Level = level };
                            retQuestion.Document.Add(myDoc);
                            GaloapDatabase.Database.AddToQuestion(retQuestion);
                        }
                        catch (InvalidOperationException)
                        {
                            throw new InvalidOperationException("No more documents on Database.");
                        }
                    }
                }
                else
                {
                    // add a new question on database if there are unused document 
                    try
                    {
                        Document myDoc = Game.GetUnusedDocument<K>();
                        retQuestion = new T { Game = this.Game, Level = level };
                        retQuestion.Document.Add(myDoc);

                        GaloapDatabase.Database.AddToQuestion(retQuestion);
                    }
                    catch (InvalidOperationException)
                    {
                        // no more NEW documents
                        try
                        {
                            // give an existing question which user hasn't replied
                            retQuestion = this.Game.Question.Except(this.GetQuestions(level)).OfType<T>().OrderBy(d => Guid.NewGuid()).First();
                        }
                        catch (InvalidOperationException)
                        {
                            // no more documents
                            throw new InvalidOperationException("No more documents on Database.");
                        }
                    }
                }
            
                // init question
                retQuestion.Initialize();
                return retQuestion;
            }
            else
                return GetQuestion<T, K>(level , probably);
            
        }

        /// <summary>
        /// method which gets  a question from database or generates and get a new question if there aren't any question available
        /// </summary>
        /// <typeparam name="T">the Question subtype</typeparam>
        /// <typeparam name="K">the Document subtype</typeparam>
        /// <param name="level">the level of the question required</param>
        /// <returns>a T question initialized</returns>
        virtual public T GetQuestion<T, K>(int level) 
            where T : Question, new() 
            where K : Document
        {
            
            this.GameReference.Load();
            this.Game.Question.Load();

            T retQuestion;
            
            try
            {
                // take a question from database
                retQuestion = this.Game.Question.Except(this.GetQuestions(level)).OfType<T>().OrderBy(d => Guid.NewGuid()).First();
            }
            catch (InvalidOperationException)
            {
                // no more question, make a new question on a document
                retQuestion = new T { Game = this.Game, Level = level };
                retQuestion.Document.Add(Document.GetDocuments<K>().OrderBy(d => Guid.NewGuid()).First());
                GaloapDatabase.Database.AddToQuestion(retQuestion);
            }
            retQuestion.Initialize();

            return retQuestion;
        }

        /// <summary>
        /// method which gets a question from database with a given probably or generates and get a new question if there aren't any question available
        /// </summary>
        /// <typeparam name="T">the Question subtype</typeparam>
        /// <typeparam name="K">the Document subtype</typeparam>
        /// <param name="level">the level of the question required</param>
        /// <param name="probably">the 0-1 float representing the probably of take an axisting question on DB</param>
        /// <returns>a T question initialized</returns>
        virtual public T GetQuestion<T, K>(int level, float probably)
            where T : Question, new()
            where K : Document
        {
            Random _r = new Random();

            this.GameReference.Load();
            this.Game.Question.Load();

            T retQuestion; 

            try
            {
                if (_r.Next(100) < probably * 100)
                {
                    // take a question from database
                    retQuestion = this.Game.Question.Except(this.GetQuestions(level)).OfType<T>().OrderBy(d => Guid.NewGuid()).First();
                }
                else
                {
                    // make a new question on a document
                    retQuestion = new T { Game = this.Game, Level = level };
                    retQuestion.Document.Add(Document.GetDocuments<K>().OrderBy(d => Guid.NewGuid()).First());

                    GaloapDatabase.Database.AddToQuestion(retQuestion);
                }
            }
            catch (InvalidOperationException)
            {
                // no more question, make a new question on a document
                retQuestion = new T { Game = this.Game, Level = level };
                retQuestion.Document.Add(Document.GetDocuments<K>().OrderBy(d => Guid.NewGuid()).First());

                GaloapDatabase.Database.AddToQuestion(retQuestion);
            }
            retQuestion.Initialize();
            return retQuestion;
        }

        /// <summary>
        /// method which assign a score of an answer and save it on DB
        /// </summary>
        /// <param name="answer">the answer replied</param>
        /// <returns>the points made in this question</returns>
        virtual public int Scoring(Answer answer)
        {
            Random r = new Random();
            float points;
            try
            {
                Answer.Load();
                int maxpoint = answer.Question.GetMostFrequentValidAnswerCount();
                if (maxpoint != 0)
                {
                    points = 10f * (Answer.Where(a => answer.Question.Equals(a.Question) && a.CompareData(answer)).First().Counter) / maxpoint + 0.49f;
                    if (points < 0)
                        points *= -1;
                }
                else points = 5;
                points += r.Next(2);
            }
            catch (InvalidOperationException)
            {
                // no answers
                points = r.Next(4) + 1;
            }
            this.Score += (int)points;
            GaloapDatabase.Database.SaveChanges();
            return (int)points;
        }

        /// <summary>
        /// List of questions asked in this GameSession at a specific level
        /// </summary>
        /// <param name="level">the level of the question asked</param>
        /// <returns>a List of question</returns>
        public List<Question> GetQuestions(int level)
        {
            Answer.Load();
            foreach (Answer answer in Answer) answer.QuestionReference.Load();
            return (from answer in Answer
                    let question = answer.Question
                    where question.Level <= level
                    select question).ToList();
        }


        /// <summary>
        /// Method which update the ranking in UserGame class. Ranking is a proberty that allows to know the reliability of an user in a game
        /// Ranking is modified after an user replies than 10 questions.
        /// </summary>
        /// <param name="givenAnswer">the answer given</param>
        /// <param name="isknown">the question is a prevent cheating question</param>
        virtual public void UpdateUserRanking(Answer givenAnswer, bool isknown)
        {
            double currentranking;
            User.UserGame.Load();
            try
            {
                // the ranking for a game is acquired after 10 question answered
                currentranking = User.GetRankingForGame(Game);
                if (GetQuestions().Count == 10)
                    currentranking = 0.6;
            }
            catch (Exception)
            {
                // not UserGame defined for this user in the game
                currentranking = 0;
                UserGame ug = new UserGame { Game = this.Game, User = this.User, ranking = currentranking };
                GaloapDatabase.Database.AddToUserGame(ug);
            }

            if (givenAnswer.Truth && isknown && currentranking < 1)
            {
                // increase ranking x 2
                currentranking *= 1.05f;
                if (currentranking > 1f)
                    currentranking = 1f;
            }
            else if (!givenAnswer.Truth && isknown)
            {
                // decrease ranking
                currentranking *= 0.4;
            }
            else // case base: !Truth && !isknown
            {
                // increase ranking 
                currentranking *= 1.025;
                if (currentranking > 1f)
                    currentranking = 1f;
            }

            try
            {
                // set ranking on database
                IEnumerable<UserGame> lg = User.UserGame.Where(d => d.Game.GameID == givenAnswer.Question.Game.GameID);
                lg.First().ranking = currentranking;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            GaloapDatabase.Database.SaveChanges();
        }

        /// <summary>
        /// method which calculates the current scoring of a player, in base of his answer and the answers given by others players
        /// </summary>
        /// <returns></returns>
        protected int UpdateUserScores()
        {
            Answer.Load();
            int newscores = 0;
                        
            // calculate the new score
            foreach (Answer answer in Answer)
            {
                newscores += (int)(10f * (Answer.Where(a => answer.Question.Equals(a.Question) && a.CompareData(answer)).First().Counter + 1) / (answer.Question.GetMostFrequentValidAnswerCount() + 1) + 0.49f);
            }

            // update the score only if the newest it greater than the oldest
            if (newscores > Score)
            {
                Score = newscores;
                GaloapDatabase.Database.SaveChanges();
                return newscores;
            }
            else return Score;
        }

        /// <summary>
        /// method which return a GameSession without update of user score
        /// </summary>
        /// <param name="u">the User of the GameSession</param>
        /// <param name="g">the Game of the GameSession</param>
        /// <returns>the GameSession required</returns>
        public static GameSession GetGameSession(User u, Game g)
        {
            return GameSession.GetGameSession(u, g, false);
        }

        /// <summary>
        /// method which return a GameSession
        /// </summary>
        /// <param name="u">the User of the GameSession</param>
        /// <param name="g">the Game of the GameSession</param>
        /// <param name="updatescore">true if it want to update user score, false otherwise</param>
        /// <returns>the GameSession required</returns>
        public static GameSession GetGameSession(User u, Game g, bool updatescore)
        {
            GameSession gs;
            try
            {
                gs = GameSession.GetLastGameSession(u, g);
                if (gs.Terminated)
                {
                    // the gamesession is terminated: make a new gamesession
                    gs = new GameSession { Terminated = false, Game = g, User = u, Level = 1, Score = 0, Date = DateTime.Now };
                    GaloapDatabase.Database.SaveChanges();
                }
                else if (updatescore)
                    gs.UpdateUserScores();
            }
            catch (InvalidOperationException)
            {
                // not exists gamesession for this user and this game
                gs = new GameSession { Terminated = false, Game = g, User = u, Level = 1, Score = 0, Date = DateTime.Now };
                GaloapDatabase.Database.SaveChanges();
            }
            return gs;
        }


    }
}
